package com.zhongwang.mms.config.shiro.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhongwang.mms.common.JsonResult;
import com.zhongwang.mms.constant.Constant;
import com.zhongwang.mms.gen.entity.User;
import com.zhongwang.mms.util.ServletUtils;
import com.zhongwang.mms.util.ShiroUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Deque;

/**
 * @Description: 登录帐号控制过滤器
 * 
 * @Author: China.sgl
 * @Date: 2019/9/10 08:38
 */
@Slf4j
public class KickOutSessionFilter extends AccessControlFilter {

    private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    /** 同一个用户最大会话数 */
    private int maxSession = -1;

    /** 踢出 之前 / 之后 登录的用户  默认false踢出之前登录的用户 */
    private Boolean kickOutAfter = false;

    /** sessionKey */
    private static final String KICK_OUT = "kickOut";

    /** 踢出后到的地址 */
    private String kickOutUrl;

    private SessionManager sessionManager;
    private Cache<String, Deque<Serializable>> cache;

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }

    public void setKickOutAfter(boolean kickOutAfter) {
        this.kickOutAfter = kickOutAfter;
    }

    public void setKickOutUrl(String kickOutUrl) {
        this.kickOutUrl = kickOutUrl;
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    public void setCacheManager(CacheManager cacheManager) {
        // 必须和ehcache缓存配置中的缓存name一致
        this.cache = cacheManager.getCache(Constant.SYS_USER_CACHE);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        // 如果没有登录或用户最大会话数为-1，直接进行之后的流程
        boolean flag = !subject.isAuthenticated() && !subject.isRemembered() || maxSession == -1;
        if (flag) {
            return true;
        }
        try {
            Session session = subject.getSession();
            // 当前登录用户
            User user = ShiroUtils.getUser();
            String loginName = user.getAccount();
            Serializable sessionId = session.getId();

            // 读取缓存用户 没有就存入
            Deque<Serializable> deque = cache.get(loginName);
            if (deque == null) {
                // 初始化队列
                deque = new ArrayDeque<>();
            }

            // 如果队列里没有此sessionId，且用户没有被踢出；放入队列
            if (!deque.contains(sessionId) && session.getAttribute(KICK_OUT) == null) {
                // 将sessionId存入队列
                deque.push(sessionId);
                // 将用户的sessionId队列缓存
                cache.put(loginName, deque);
            }

            // 如果队列里的sessionId数超出最大会话数，开始踢人
            while (deque.size() > maxSession) {
                Serializable kickOutSessionId;
                // 是否踢出后来登录的，默认是false；即后者登录的用户踢出前者登录的用户；
                if (kickOutAfter) {
                    // 踢出后者
                    kickOutSessionId = deque.removeFirst();
                } else {
                    // 踢出前者
                    kickOutSessionId = deque.removeLast();
                }
                // 踢出后再更新下缓存队列
                cache.put(loginName, deque);

                try {
                    // 获取被踢出的sessionId的session对象
                    Session kickOutSession = sessionManager.getSession(new DefaultSessionKey(kickOutSessionId));
                    if (null != kickOutSession) {
                        // 设置会话的kickOut属性表示踢出了
                        kickOutSession.setAttribute(KICK_OUT, true);
                    }
                } catch (Exception e) {
                    log.info(e.toString());
                }
            }

            // 如果被踢出了，(前者或后者)直接退出，重定向到踢出后的地址
            if (session.getAttribute(KICK_OUT) != null && (Boolean) session.getAttribute(KICK_OUT)) {
                // 退出登录
                subject.logout();
                saveRequest(request);
                return isAjaxResponse(request, response);
            }
            return true;
        } catch (Exception e) {
            return isAjaxResponse(request, response);
        }
    }

    private boolean isAjaxResponse(ServletRequest request, ServletResponse response) throws IOException {

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        if (ServletUtils.isAjaxRequest(req)) {
            JsonResult ajaxResult = JsonResult.fail("您已在别处登录，请您修改密码或重新登录");
            ServletUtils.renderString(res, OBJECT_MAPPER.writeValueAsString(ajaxResult));
        } else {
            WebUtils.issueRedirect(request, response, kickOutUrl);
        }
        return false;
    }


}
